/* 
 * Copyright (C) 2007  Brunata a/s
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/.
 */

package com.brunata.httprmi;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.*;
import javax.servlet.http.*;

/**
 * The base servlet that plays out the gateway role to a matching Proxy. It will 
 * process incoming requests and devise a response. Subclass this class and implement
 * your contract.
 * 
 * @author Casper Bang (cab@brunata.com)
 */
public class HttpRmiServlet extends HttpServlet
{
    ////////////////////////////////////////////////////////////////////////////
    // Constants
    ////////////////////////////////////////////////////////////////////////////
    
    ////////////////////////////////////////////////////////////////////////////
    // Constructors
    ////////////////////////////////////////////////////////////////////////////

    public HttpRmiServlet()
    {
        super();
    }
       
    ////////////////////////////////////////////////////////////////////////////
    // Overrides/implementations
    ////////////////////////////////////////////////////////////////////////////
    
    /** Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * 
     * @param request servlet request
     * @param response servlet response
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException
    {
        // TODO: Check user-agent, such that we can return textual representation of query result when browsers are used!
        Logger.getLogger("global").log(Level.INFO, request.getQueryString());

        // Ensure browser invocation does not set request content length to -1
        int reqContentLength = (request.getContentLength() == -1) ? 0: request.getContentLength();
        byte[] inputBuffer = new byte[reqContentLength];
        Object[] arguments = null;
        byte[] outputBuffer = null;
        Object replyObject = null;
        ObjectInputStream in = null;
        
        try
        {
            // Get a hold of POST content
            inputBuffer = getPostData( request );

            Logger.getLogger("global").log(Level.INFO, "Successfully read POST data");

            // Deserialize POST content into a request parameter Object
            in = new ObjectInputStream(new ByteArrayInputStream(inputBuffer));
            arguments = (Object[]) in.readObject();            
        }
        catch(EOFException ex)
        {
            // No arguments in POST content, so just ignore and continue
        }
        catch(IOException ex)
        {
            // Server side exception, prepare to notify proxy with a HttpRmiException
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        catch (ClassNotFoundException ex) 
        {
            // Server side exception, prepare to notify proxy with a HttpRmiException
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        finally 
        {
            try
            {
                in.close();
            }
            catch(Exception ex)
            {
            }
        }

        // Ok new we have the arguments, invoke specified method
        
        try 
        {
            Method methodToInvoke = this.getClass().getMethod(
                    request.getParameter("ACTION"), 
                    getParameterTypes(arguments));

            replyObject = methodToInvoke.invoke(this, arguments);
        }
        catch (IllegalAccessException ex) 
        {
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        catch (IllegalArgumentException ex) 
        {
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        catch (InvocationTargetException ex) 
        {
            replyObject = new HttpRmiException("Internal error: Notify administrative personel that required data access could not be uptained.");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        catch (NoSuchMethodException ex) 
        {
            replyObject = new HttpRmiException("Method " + request.getParameter("ACTION") + " not implemented (with these arguments)!");
            Logger.getLogger("global").log(Level.SEVERE, null, replyObject);
        }
        catch (SecurityException ex) 
        {
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        catch(Exception ex)
        {
            replyObject = new HttpRmiException("");
        }
        
        // Serialize the result Object of the call into a byte array
        ObjectOutput out = null;
        try
        {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            out = new ObjectOutputStream(bos);
            out.writeObject(replyObject);
            outputBuffer = bos.toByteArray();
        }
        catch(IOException ex)
        {
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        finally
        {
            try
            {
                out.close();
            }
            catch(IOException ex)
            {
            }
        }
        
        // Send the result Object back as a HTTP reply
        Logger.getLogger("global").log(Level.FINEST, null, "Sending: " + outputBuffer.length + " bytes");
        
        response.setContentType("application/octet-stream");
        response.setContentLength(outputBuffer.length);
        ServletOutputStream sos = null;
        try
        {
            sos = response.getOutputStream();
            sos.write(outputBuffer);
        }
        catch(IOException ex)
        {
            replyObject = new HttpRmiException("");
            Logger.getLogger("global").log(Level.SEVERE, null, ex);
        }
        finally
        {
            try
            {
                sos.close();
            }
            catch(IOException ex)
            {
            }
        }
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Internals
    ////////////////////////////////////////////////////////////////////////////

    private byte[] getPostData(HttpServletRequest req) throws IOException
    {
        // Lets fetch POST content (if any) and place into an input buffer
        int contentLength = req.getContentLength();
        byte[] inputBuffer = new byte[contentLength];
        ServletInputStream sis = req.getInputStream();
        sis.read(inputBuffer, 0, contentLength);
        sis.close();
        return inputBuffer;
    }
    
    /**
     * This helper method will extract the class names from an object array and
     * return a corresponding class array
     */ 
    private static final Class<?>[] getParameterTypes(Object[] parameters)
    {
        if(parameters == null)
            return  new Class<?>[ 0 ];
        else
        {
            Class<?>[] parmTypes = new Class<?>[ parameters.length ];
            for(int i = 0; i < parameters.length; i++)
                parmTypes[i] = parameters[i].getClass();
            return parmTypes;
        }
    }
    
    /** Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        processRequest(request, response);
    }
    
    /** Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
    {
        processRequest(request, response);
    }
    
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() 
    {
        return "Short description";
    }
    
    ////////////////////////////////////////////////////////////////////////////
    // Accessors (getters/setters)
    //////////////////////////////////////////////////////////////////////////// 
}
